package com.sqi.reactive.common.filter

import com.sqi.reactive.common.annoation.IgnoreAuth
import com.sqi.reactive.common.auth.AuthPayload
import com.sqi.reactive.common.auth.RequestContext
import com.sqi.reactive.common.token.TokenHelper
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.util.StringUtils
import org.springframework.web.method.HandlerMethod
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono


/**
 *
 *
 * @author: zhangfx
 *
 * @create: 2020-05-15 11:01
 **/
@Suppress("SpringJavaAutowiredMembersInspection")
class AuthFilter: WebFilter {
    private val log = LoggerFactory.getLogger(this.javaClass)

    @Autowired
    lateinit var requestMappingHandlerMapping: RequestMappingHandlerMapping

    @Value("\${auth.token.key:token}")
    lateinit var tokenKey: String

    @Value("\${auth.basePackages:com.sqi}")
    lateinit var baseAuthPackageArray: Array<String>

    @Value("\${auth.errorUrl:/auth/error}")
    lateinit var errorUrl: String

    @Value("\${auth.ignoreUrls:}")
    lateinit var ignoreUrlArray: Array<String>

    @Autowired
    private lateinit var requestContext: RequestContext

    @Autowired
    private lateinit var tokenHelper: TokenHelper

    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void>? {
        val handler = requestMappingHandlerMapping.getHandler(exchange).toProcessor().peek()
        var path = exchange.request.path.toString()
        log.debug(
            "#########[WebfluxAuthFilter.filter] path={}, queryString={}, handler={}",
            path,
            exchange.request.queryParams.toSingleValueMap().toString(),
            handler
        )
        //注意跨域时的配置，跨域时浏览器会先发送一个option请求，这时候getHandler不会时真正的HandlerMethod
        if (handler is HandlerMethod) {
            // 判断是否在权限控制范围
            val packageName = handler.beanType.`package`.name
            log.debug(
                "########[WebfluxAuthFilter.filter] path={}, handler.pageckageName={}, handler={}",
                path,
                packageName,
                handler
            )
            if (handler.method.getAnnotation(IgnoreAuth::class.java) != null) {
                log.debug("########[WebfluxAuthFilter.filter] 不在控制package之内， path={}", path)
                return chain.filter(exchange);
            }
            if (!exchange.check(packageName)) {
                log.debug("########[WebfluxAuthFilter.filter] 不在控制package之内， path={}", path)
                return chain.filter(exchange);
            }
            var payload = exchange.loadPayLoad()
            if (payload == null) {
                val authErrorReq: ServerHttpRequest = exchange.request.mutate().path(errorUrl).build()
                val authErrorExchange = exchange.mutate().request(authErrorReq).build()
                return chain.filter(authErrorExchange)
            }
        }
        log.debug("[WebfluxAuthFilter.filter]>>curr Thread: {}", Thread.currentThread().name)
        return chain.filter(exchange)
    }

    /**
     * 获取token
     */
    private fun getToken(request: ServerHttpRequest, tokenKey: String): String? {
        var token = request.headers.getFirst(tokenKey)
        if (StringUtils.isEmpty(token)) {
            for (cookie in request.cookies) {
                if (cookie.key == tokenKey) {
                    val httpCookie = cookie.value.getOrNull(0)
                    if (httpCookie != null) {
                        token = httpCookie.value
                    }
                    break
                }
            }
        }
        if (StringUtils.isEmpty(token)) {
            token = request.queryParams.getFirst(tokenKey)
        }
        if (StringUtils.isEmpty(token)) {
            token = null
        }
        return token
    }

    private fun ServerWebExchange.loadPayLoad(): AuthPayload? {
        val token = getToken(this.request, tokenKey)

        val payload = tokenHelper.verify(token)
        //设置请求payload到上下文中
        if (payload != null) {
            requestContext.authPayload = payload
        }
        return payload
    }

    private fun ServerWebExchange.check(packageName: String): Boolean {
        return baseAuthPackageArray.any { packageName.startsWith(it) }
                && ignoreUrlArray.all {
            it != this.request.path.toString()
        }
    }
}